package com.qzdsoft.erpcloud.service.sys.impl;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import com.qzdsoft.erpcloud.domain.sys.SysLog;
import com.qzdsoft.utils.ExceptionUtil;
import com.qzdsoft.utils.StringUtil;

@Component  
@Aspect
public class LogRecordAop
{

    private static final Logger logger = LoggerFactory.getLogger(LogRecordAop.class);
    @Autowired
    private SysLogServiceImpl sysLogService;
    public static final ExecutorService logExecutor = Executors.newFixedThreadPool(5); 
  //匹配com.qzdsoft.erpcloud.controller包及其子包下的所有类的所有方法  
    @Pointcut("@annotation(com.qzdsoft.erpcloud.service.sys.SysLog)")  
    public void executeService(){  
  
    }  
    /** 
     * 环绕通知： 
     *   环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。 
     *   环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 
     */  
    @Around("executeService()")  
    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){ 
        try {//obj之前可以写目标方法执行前的逻辑  
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
            Integer uid = getUid(request);
            Long startTime = System.currentTimeMillis(); 
            String ip = getIp(request);
            Object obj = proceedingJoinPoint.proceed();//调用执行目标方法  
            Long endTime = System.currentTimeMillis(); 
            logExecutor.submit(new LoggerRecord(proceedingJoinPoint, (int)(endTime-startTime),ip,uid));
            return obj;  
        } catch (Exception ex) {  
            logger.error("日志记录错误，错误信息：{}",ExceptionUtil.getStackTrace(ex));
        }
        catch (Throwable e)
        {
            logger.error("日志记录错误，错误信息x：{}",e);
            e.printStackTrace();
        }  
        return null;  
    } 
    
    /** 
     * 获取注解中对方法的描述信息 用于Controller层注解 
     * 
     * @param joinPoint 切点 
     * @return 方法描述 
     * @throws Exception 
     */  
     public  static String getMethodDescription(ProceedingJoinPoint joinPoint)  throws Exception {  
        String targetName = joinPoint.getTarget().getClass().getName();  
        String methodName = joinPoint.getSignature().getName();  
        Object[] arguments = joinPoint.getArgs();  
        Class targetClass = Class.forName(targetName);  
        Method[] methods = targetClass.getMethods();  
        String description = "";  
         for (Method method : methods) {  
             if (method.getName().equals(methodName)) {  
                Class[] clazzs = method.getParameterTypes();  
                 if (clazzs.length == arguments.length) {  
                    description =  method.getAnnotation(com.qzdsoft.erpcloud.service.sys.SysLog.class).description();  
                     break;  
                }  
            }  
        }  
         return description;  
    }  
  
    private class LoggerRecord implements Runnable
    {
        private ProceedingJoinPoint proceedingJoinPoint;

        private int time;
        private String ip;
        private Integer uid;

        public LoggerRecord(ProceedingJoinPoint proceedingJoinPoint, int time,String ip,Integer uid)
        {
            this.proceedingJoinPoint = proceedingJoinPoint;
            this.time = time;
            this.uid = uid;
            this.ip = ip;
        }

        public void run()
        {
            try
            {
                SysLog log = new SysLog();
                Object args[] = proceedingJoinPoint.getArgs();
                String className = proceedingJoinPoint.getSignature().getDeclaringTypeName();
                log.setMethod(className + "." + proceedingJoinPoint.getSignature().getName());
                StringBuffer buffer = new StringBuffer();
                for (int i = 0; i < args.length; i++)
                {
                    if (i != args.length - 1)
                    {
                        buffer.append(args[i] + ",");
                    }
                    else
                    {
                        buffer.append(args[i]);
                    }
                }
                log.setArgs(buffer.toString());
                log.setCtime(new Date());
                log.setIp(ip);
                log.setDescription(getMethodDescription(proceedingJoinPoint));
                log.setUid(uid);
                log.setExecuteTime(time);
                sysLogService.insert(log);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                logger.error("日志记录错误，错误信息：{}", ExceptionUtil.getStackTrace(e));
            }
        }
    }
    private String getIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if(!StringUtil.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            //多次反向代理后会有多个ip值，第一个ip才是真实ip
            int index = ip.indexOf(",");
            if(index != -1){
                return ip.substring(0,index);
            }else{
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if(!StringUtil.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){
            return ip;
        }
        return request.getRemoteAddr();
    }
    private Integer getUid(HttpServletRequest request) {
        Integer uid = null;
        Cookie cookies[] = request.getCookies();
        if(cookies==null) {
            return null;
        }
        for (Cookie c : cookies)
        {
            if ("uid".equals(c.getName()))
            {
                uid = Integer.parseInt(c.getValue());
                break;
            }
        }
        return uid;
    }
}
